Litestream: replicación casi-tiempo-real para SQLite

Cascada fluida de agua representando flujo continuo de datos

Durante años, quien ponía SQLite en producción asumía un pacto con el diablo: rendimiento excelente, operativa trivial, coste casi cero, pero copia de seguridad incómoda. Cron, sqlite3 .backup, rsync cada hora, algún script subiendo el fichero a un bucket y cruzar los dedos. Litestream, publicado por Ben Johnson en 2021 y estable en la rama 0.3, cierra ese hueco: lee el log WAL que SQLite ya escribe y lo sube, página a página, a un bucket compatible con S3. El resultado es una base de datos local con durabilidad equivalente a la de servicios gestionados, sin servidor adicional ni replicación configurada a mano.

Qué resuelve realmente

La propuesta no es sustituir a Postgres ni competir con un cluster distribuido. Es tapar el único agujero serio del patrón “SQLite más disco local”: la pérdida del host. Con Litestream como proceso acompañante, cada transacción que entra al WAL se empaqueta en segmentos y se envía al almacenamiento remoto con un retraso típico por debajo del segundo. Si la máquina se evapora, el operador arranca otra, restaura desde el bucket y está donde estaba con apenas unos segundos de datos en riesgo. Es el valor de una réplica síncrona a cambio de 1-3% de CPU y unos pocos MB de RAM.

La diferencia conceptual frente a Postgres o MySQL gestionados es que Litestream no introduce un segundo nodo vivo. No hay failover automático ni lectura distribuida ni consistencia multi-máquina. Lo que hay es una secuencia ordenada de snapshots y segmentos de WAL en S3 que permite reconstruir el estado de la base en cualquier instante reciente. Encaja con el 80% de aplicaciones que, siendo honestos, podrían funcionar en una sola máquina con SQLite y nunca notar la limitación.

Cómo funciona por dentro

Litestream se aprovecha de que SQLite en modo WAL ya escribe los cambios en un fichero aparte antes de consolidarlos al .db principal. El agente abre ese WAL en modo lectura, lo sigue como si fuera un tail -f binario y, cada cierto intervalo, empaqueta los bytes nuevos en un segmento con un nombre determinista y lo sube al bucket. Periódicamente toma un snapshot completo para que la restauración no tenga que aplicar un WAL infinito desde el principio de los tiempos.

El detalle fino es que Litestream retiene una transacción abierta sobre la base. Eso impide que SQLite haga checkpoint del WAL hasta que los segmentos correspondientes estén confirmados en S3. Garantía sutil pero crítica: nunca se pierde historia por un checkpoint prematuro. El coste es que el WAL crece mientras la red al bucket esté caída, cosa que conviene monitorizar.

La instalación es un único binario en Go, sin dependencias, que se gestiona como servicio systemd o como sidecar en un contenedor. La configuración vive en un YAML pequeño donde se declaran las bases a replicar y los destinos:

dbs:
  - path: /data/app.db
    replicas:
      - type: s3
        bucket: mi-app-backups
        path: app.db
        region: eu-west-1
        retention: 72h

Con eso y las credenciales en variables de entorno, el agente empieza a replicar. El destino puede ser AWS S3, pero también MinIO autoalojado, Cloudflare R2 (muy interesante por su coste de egress cero), Backblaze B2, Hetzner Object Storage o Wasabi. Cualquier endpoint compatible con la API S3 sirve, y nada impide declarar varios simultáneamente para tener redundancia entre proveedores: un bucket en AWS, otro en R2, y una copia a disco local para recuperaciones instantáneas. Si un proveedor sufre una caída prolongada, el otro sigue siendo una fuente válida de restauración.

Recuperación y punto en el tiempo

Restaurar es una sola llamada. El binario descarga el último snapshot, aplica los segmentos posteriores del WAL hasta el punto deseado y escribe el fichero .db resultante en la ruta indicada. En bases pequeñas hablamos de segundos; en bases de varios GB, de algunos minutos limitados más por el ancho de banda que por la lógica. La recuperación a punto en el tiempo acepta un timestamp arbitrario dentro del periodo de retención configurado, algo tremendamente útil cuando alguien ejecuta un DELETE accidental y hay que volver treinta minutos atrás sin perder lo que se escribió después fuera de la tabla afectada.

La cadencia entre snapshots es una palanca importante. Snapshots más frecuentes significan restauraciones más rápidas pero más almacenamiento; snapshots espaciados abaratan el bucket pero alargan el tiempo de recuperación. Para la mayoría de aplicaciones, un snapshot cada 24 horas y una retención de 72 horas funciona bien y deja los costes en céntimos al mes.

Cuándo gana a Postgres gestionado

El eje de decisión honesto no es técnico sino operativo. Un Postgres gestionado parte de veinte o treinta euros al mes por la instancia más pequeña, sube al añadir réplicas y obliga a pensar en pooler, versión mayor, ventanas de mantenimiento y migraciones. SQLite con Litestream vive dentro del propio proceso de la aplicación y añade al presupuesto únicamente el coste del bucket, por debajo del euro mensual en la mayoría de casos.

La ventaja desaparece en cuanto hace falta escritura concurrente desde procesos distintos, extensiones específicas de Postgres como pg_trgm o búsqueda vectorial, lecturas distribuidas geográficamente con consistencia garantizada, o cumplimiento que exija un motor concreto. En esos casos no hay discusión. Pero el volumen de aplicaciones que de verdad necesitan esas capacidades es mucho menor de lo que la industria ha asumido durante años.

Litestream frente a LiteFS

Conviene no confundir proyectos. LiteFS, del mismo autor bajo el paraguas de Fly.io, es una capa FUSE que replica SQLite entre varios nodos en tiempo real con consistencia fuerte; resuelve un problema distinto, el de la replicación activa multi-nodo, a cambio de complejidad operativa considerable. Litestream, en cambio, se queda deliberadamente en el escenario de un único escritor y traslada todo el valor al bucket como sistema de durabilidad. Quien necesita lecturas desde múltiples regiones con baja latencia mira LiteFS o directamente una base distribuida; quien solo quiere dormir tranquilo cuando su VPS se reinicie, con Litestream tiene suficiente.

Operación y observabilidad

El agente expone métricas Prometheus en un puerto configurable. Las que importan son las que miden el retraso entre escritura local y confirmación en S3, el volumen de bytes replicados y el tiempo desde la última sincronización satisfactoria. Una alerta sobre “última sincronización hace más de cinco minutos” basta para detectar problemas de red, permisos del bucket o cuotas agotadas antes de que se conviertan en pérdida real de datos. El ensayo trimestral de restauración en un entorno de staging es la segunda pieza imprescindible: una copia de seguridad que no se prueba acaba, tarde o temprano, por no funcionar cuando hace falta.

Conclusión

Litestream es una herramienta madura que representa algo más amplio: la rehabilitación de SQLite como motor adecuado para producción real cuando la carga lo permite. Durante una década la industria asumió que “serio” implicaba un servidor de base de datos aparte, con su propia operativa, sus propias copias y sus propias complicaciones. El resultado fue arquitecturas sobredimensionadas para problemas que no lo eran. Proyectos como Basecamp, PocketBase y el propio Fly.io demuestran que muchas aplicaciones viven mejor con un fichero local bien replicado que con una instancia remota bien cara. Para el stack del desarrollador solitario, del SaaS pequeño o del servicio interno que no necesita disfraz, Litestream no es un atajo: es la elección técnicamente correcta.

Entradas relacionadas